home *** CD-ROM | disk | FTP | other *** search
/ The Arsenal Files 6 / The Arsenal Files 6 (Arsenal Computer).ISO / sync / xsdk_241.zip / XSDK.C < prev    next >
Text File  |  1996-02-01  |  71KB  |  2,435 lines

  1. /* XSDK.C */
  2.  
  3. /****************************************************************************/
  4. /*            Synchronet External Program Software Development Kit            */
  5. /*                            1995 Digital Dynamics                            */
  6. /****************************************************************************/
  7.  
  8. /****************************************************************************/
  9. /* This source code file is public domain and may be modified, compiled     */
  10. /* distributed, or used in any way, in part or whole for any purposes        */
  11. /* without the consent or notification of Digital Dynamics.                 */
  12. /*                                                                            */
  13. /* We only request that you display to the user, at some point, in your     */
  14. /* program the character "XSDK" and the version number.                     */
  15. /* example: bprintf("XSDK v%s",xsdk_ver);                                   */
  16. /****************************************************************************/
  17.  
  18. /****************************************************************************/
  19. /* The source code for two external programs developed by Digital Dynamics    */
  20. /* using XSDK (Synchronet Blackjack [SBJ] and Synchronet BBS List [SBL])    */
  21. /* are available to the public domain as examples of how to implement the    */
  22. /* functions and variables included in this software development kit.        */
  23. /****************************************************************************/
  24.  
  25. /****************************************************/
  26. /* For use with Borland/Turbo C and C++ compilers.    */
  27. /* Tabstop set to 4.                                */
  28. /****************************************************/
  29.  
  30. /***************************** Revision History *****************************\
  31.  
  32.             Initial version for use with Synchronet v1a r6
  33.     1.0ß    
  34.             Added bgotoxy() macro
  35.             Added mnehigh and mnelow vars for control of the mnemonic colors
  36.             Added sys_nodes and node_num variables to xtrn_sdk.c
  37.             Added MAX_NODES to xtrn_sdk.h
  38.             Added printfile() function to xtrn_sdk.c
  39.             Added rputs() (Raw put string)
  40.             Added getstr() (Get string)
  41.             Added redrwstr() (Redraw string)
  42.             Added stripattr() (String attributes)
  43.             Added sys_op var and the reading from the xtrn.dat
  44.             Removed user_min and the reading from the xtrn.dat
  45.             Changed read_xtrn_dat() to initdata()
  46.             Added ctrl-break handler to xtrn_sdk
  47.             Changed xtrn.dat format (again), adding system operator,
  48.                 guru name, user ml, tl, birthdate and sex.
  49.             Added username() function
  50.             Renamed xtrn_sdk.* to xsdk.* and xtrnvars.c to xsdkvars.c
  51.                 and xtrndefs.h to xsdkdefs.h
  52.             Added fexist() function
  53.     1.0
  54.             Ctrl-p is now switched into ctrl-^ by SBBS
  55.             Fixed relative data_dir bug in username()
  56.     1.01
  57.             Added flength() function and lowered disk activity
  58.             Lowered MAX_CARDS from 20 to 10 and made the re-shuffling happen
  59.                 less often.
  60.     1.02
  61.             Fixed bug in attr() for monochrome users
  62.     1.03
  63.             Made warning and timeout times variables (sec_warn and sec_timeout)
  64.             Added SYSOP macro
  65.             Made it so sysop won't get timeout
  66.             Added user's phone number to XTRN.DAT
  67.             Added modem and com port information to XTRN.DAT
  68.             Added ahtoul function
  69.             Changed getstr commands Ctrl-L to Ctrl-R and Ctrl-P to Ctrl-\
  70.     1.04
  71.             Added intercommunication between the external programs and users
  72.             on the BBS or other external programs written with XSDK.
  73.             Added rprintf() function
  74.             Made many changes to getstr() function
  75.     2.00
  76.             Added DESQview awareness
  77.             Removed difftime() function calls completely
  78.             Added ungetkey() function
  79.             Added memory address of last modem status register for com routines
  80.                 so we can track DCD incase user hangs up.
  81.             Added checkline function that does the checking of DCD.
  82.             Added new bug-free fdelay() routine to replace TC's delay() that
  83.                 crashes multi-taskers such as DESQview and Windows
  84.     2.01
  85.             Added external program name access for user listings.
  86.             Added last node to send message to remembering and defaulting.
  87.             Added MALLOC and FREE macros for memory model non-specific memory
  88.                 allocation.
  89.     2.02
  90.             Added INTRSBBS.DAT support for Synchronet shrinking to run programs
  91.                 written with XSDK (new with v1b rev 01).
  92.             Added user's main flags, transfer flags, exemptions, and
  93.                 restrictions to XTRN.DAT
  94.             Added support for the NODE_PAGE action (paging for private chat)
  95.                 when listing nodes (printnodedat()).
  96.             Added user expiration date support to XTRN.DAT
  97.     2.03
  98.             Fixed bug with com_base variable being messed up.
  99.             New messaging system supported (for v1b r2 and higher)
  100.                 (putnmsg and getnmsg functions).
  101.     2.10
  102.             Added support for file retrieving node status display.
  103.             NOPEN collision notice only appears after 25 retries.
  104.     2.11
  105.             Changed getnmsg function to not use IXB files.
  106.             Changed getsmsg function to not re-open for truncate.
  107.             Added user address, location, and zip/postal code suppport.
  108.             Added support for local arrow keys, home, end, ins, and del.
  109.             Remote keys ^] (back one space) and ^BkSpc (del) supported now.
  110.             Added support for high-bit Ctrl-A codes (for cursor positioning)
  111.             Removed file locking check - slowed down initialization sometimes.
  112.             Change user_ml to user_level, removed user_tl, changed user_mf to
  113.                 user_flags1, changed user_tf to user_flags2, and added
  114.                 user_flags3 and user_flags4.
  115.             cls() now updates lncntr like it should have.
  116.             Added ctrl-break handler so that users can abort printfile()
  117.             If a ctrl-c is received by inkey, the aborted flag is set.
  118.             Removed fdelay from XSDK and replaced with mswait for better
  119.                 multitasker performance
  120.     2.20
  121.             New mswait that support OS2/Windows, DOS Idle, and non-DV modes.
  122.             XTRN.DAT passes mode of mswait configured in node setup.
  123.     2.21
  124.             Added user's real name/company name (user_realname) to XTRN.DAT
  125.     2.22
  126.             Added usernumber() function to get user number from a user name.
  127.     2.23
  128.             DTE rate (com_rate) now a ulong (instead of uint) to allow 115.2K
  129.     2.24
  130.             New K_AUTODEL mode for getstr() function, to be used in conjunction
  131.                 with the K_EDIT mode. Makes overwriting existing strings very
  132.                 easy for users.
  133.             Supports intelligent timeslice APIs while waiting for a key with
  134.                 getkey() and with getstr() if K_LOWPRIO mode is used.
  135.             Hitting Ctrl-C sets the 'aborted' variable to 1.
  136.             Time zone and daylight savings automatically initialized to 0.
  137.             Modem strings up to 63 chars in XTRN.DAT now supported.
  138.             Fixed 10 character zip code bug in XSDKVARS.C.
  139.             Node directories (node_dir) up to 127 chars now supported.
  140.             nopen() can now open DENYNONE if passed access O_DENYNONE.
  141.     2.30
  142.             Added support for the following Ctrl-A codes: ;,.<>[]A
  143.             Changed definitions of TAB, SP, etc. to hex
  144.     2.31
  145.             C restriction disallows users to use Ctrl-P.
  146.             T exemption disables "Time's up" message.
  147.             Added center() function for outputting centered lines of text.
  148.             Added auto-pause to cls() and outchar() functions when clearing
  149.                 the screen and lncntr is greater than 1
  150.             Changed bstrlen() to not count control characters (CR,LF,TAB,etc)
  151.             XSDK is now Watcom C++ compatible (although SBJ.C and SBL.C aren't)
  152.             XSDK.H is now *.CPP compatible
  153.             Added support for Ctrl-AQ (reset pause) and Ctrl-AZ (premature EOF)
  154.     2.32
  155.             Change bstrlen(char *str) to bstrlen(uchar *str)
  156.             Fixed bug in getstr() when word-wrapping a line that contains
  157.                 ctrl-a codes and the input string did not begin at column 1.
  158.             Added user_dce variable (initialized by initdata from XTRN.DAT)
  159.             Fixed printnodedat() to not show Waiting for call (M)
  160.             Fixed typo in C restriction Ctrl-P message.
  161.             Moved call of checkline() in getkey() to immediately abort when
  162.                 user hangs-up, even when keys are in the input buffer.
  163.             Added setmode() call to initdata() to set stderr to binary mode
  164.             Changed putchar() to write() in outchar() to elminate LF to CRLF
  165.                 expansion
  166.     2.33
  167.             Improved cls() routine for auto-pause feature.
  168.             Added get_term() automatic RIP and WIP terminal detection function.
  169.     2.34
  170.             Added exec_dir, text_dir, and temp_dir variables to XTRN.DAT
  171.                 format and initdata() function.
  172.             Added _fullpath() calls to initdata() to fix dir paths.
  173.             Added sys_id (QWK ID) to XTRN.DAT format and initdat() func.
  174.             Added node_misc to XTRN.DAT format and initdata() function.
  175.             If NM_LOWPRIO (low priority string input) is toggled on in
  176.                 node_misc, then time-slices are always given up during input.
  177.             XSDK is now Symantec C++ compatible
  178.     2.40
  179.             node_misc was being read as a decimal number (it's stored in
  180.                 the XTRN.DAT as hex), thus causing time-slice APIs to not
  181.                 function correctly.
  182.     2.41
  183.  
  184. \****************************************************************************/
  185.  
  186. #include "xsdk.h"
  187.  
  188. char *xsdk_ver="2.41";
  189.  
  190. #ifdef __TURBOC__
  191. extern long timezone=0L;
  192. extern daylight=0;
  193. #endif
  194.  
  195. #ifdef __SC__
  196. #include <disp.h>
  197. short wherey(void);
  198. void clrscr(void);
  199. #endif  
  200.  
  201.  
  202. /****************************************************************************/
  203. /* This allows users to abort the listing of text by using Ctrl-C           */
  204. /****************************************************************************/
  205. int cbreakh(void)    /* ctrl-break handler */
  206. {
  207. aborted=1;
  208. return(1);        /* 1 to continue, 0 to abort */
  209. }
  210.  
  211. /****************************************************************************/
  212. /* Performs printf() using bbs bputs function                                */
  213. /****************************************************************************/
  214. int bprintf(char *fmt, ...)
  215. {
  216.     va_list argptr;
  217.     char sbuf[1024];
  218.     int chcount;
  219.  
  220. va_start(argptr,fmt);
  221. chcount=vsprintf(sbuf,fmt,argptr);
  222. va_end(argptr);
  223. bputs(sbuf);
  224. return(chcount);
  225. }
  226.  
  227. /****************************************************************************/
  228. /* Performs printf() using bbs rputs function                                */
  229. /****************************************************************************/
  230. int rprintf(char *fmt, ...)
  231. {
  232.     va_list argptr;
  233.     char sbuf[1024];
  234.     int chcount;
  235.  
  236. va_start(argptr,fmt);
  237. chcount=vsprintf(sbuf,fmt,argptr);
  238. va_end(argptr);
  239. rputs(sbuf);
  240. return(chcount);
  241. }
  242.  
  243. /****************************************************************************/
  244. /* Outputs a NULL terminated string locally and remotely (if applicable)     */
  245. /****************************************************************************/
  246. void bputs(char *str)
  247. {
  248.     ulong l=0;
  249.  
  250. while(str[l] && !aborted) {
  251.     if(str[l]==1) {                /* ctrl-a */
  252.         ctrl_a(str[++l]);        /* skip the ctrl-a */
  253.         if(str[l]=='Z')         /* Ctrl-AZ marks premature end of file */
  254.             break;
  255.         l++; }                    /* skip the attribute code */
  256.     else
  257.         outchar(str[l++]); }
  258. }
  259.  
  260. /****************************************************************************/
  261. /* Outputs a NULL terminated string locally and remotely (if applicable)     */
  262. /* Does not process ctrl-a codes (raw output)                                */
  263. /* Max length of str is 64 kbytes                                            */
  264. /****************************************************************************/
  265. void rputs(char *str)
  266. {
  267.     ulong l=0;
  268.  
  269. while(str[l])
  270.     outchar(str[l++]);
  271. }
  272.  
  273. /****************************************************************************/
  274. /* Returns the number of characters in 'str' not counting ctrl-ax codes        */
  275. /* or the null terminator                                                    */
  276. /****************************************************************************/
  277. int bstrlen(uchar *str)
  278. {
  279.     int i=0;
  280.  
  281. while(*str) {
  282.     if(*str<SP) {    /* ctrl char */
  283.         if(*str==1) /* ctrl-A */
  284.             str++;
  285.         else if(*str!=CR && *str!=LF && *str!=FF)
  286.             i++; }
  287.     else
  288.         i++;
  289.     if(!(*str))
  290.         break;
  291.     str++; }
  292. return(i);
  293. }
  294.  
  295. /****************************************************************************/
  296. /* Outputs the string 'str' centered for an 80 column display               */
  297. /* Automatically appends "\r\n" to output                                   */
  298. /****************************************************************************/
  299. void center(char *str)
  300. {
  301.      int i,j;
  302.  
  303. j=bstrlen(str);
  304. for(i=0;i<(80-j)/2;i++)
  305.     outchar(SP);
  306. bputs(str);
  307. }
  308.  
  309. /****************************************************************************/
  310. /* Outputs one character to the screen. Handles, pause, saving and            */
  311. /* restoring lines, etc.                                                    */
  312. /****************************************************************************/
  313. void outchar(char ch)
  314. {
  315.  
  316. write(fileno(con_fp),&ch,1);
  317.  
  318. if(ch==LF) {
  319.     lncntr++;
  320.     lbuflen=0;
  321.     tos=0; }
  322. else if(ch==FF) {
  323.     if(lncntr>1) {
  324.         lncntr=0;
  325.         CRLF;
  326.         pause(); }
  327.     lncntr=0;
  328.     lbuflen=0;
  329.     tos=1; }
  330. else if(ch==BS) {
  331.     if(lbuflen)
  332.         lbuflen--; }
  333. else {
  334.     if(!lbuflen)
  335.         latr=curatr;
  336.     if(lbuflen>=LINE_BUFSIZE) lbuflen=0;
  337.     lbuf[lbuflen++]=ch; }
  338. if(lncntr==user_rows-1) {
  339.     lncntr=0;
  340.     pause(); }
  341. }
  342.  
  343. /****************************************************************************/
  344. /* Prints PAUSE message and waits for a key stoke                            */
  345. /****************************************************************************/
  346. void pause(void)
  347. {
  348.     uchar tempattrs=curatr,*msg="\1_\1r\1h[Hit a key] ";
  349.     int i,j;
  350.  
  351. lncntr=0;
  352. bputs(msg);
  353. j=bstrlen(msg);
  354. getkey(0);
  355. for(i=0;i<j;i++)
  356.     bputs("\b \b");
  357. attr(tempattrs);
  358. }
  359.  
  360. /****************************************************************************/
  361. /* Prompts user for Y or N (yes or no) and CR is interpreted as a Y            */
  362. /* Returns 1 for Y or 0 for N                                                */
  363. /* Called from quite a few places                                            */
  364. /****************************************************************************/
  365. char yesno(char *str)
  366. {
  367.     char ch;
  368.  
  369. bprintf("\1_\1b\1h%s (Y/n) ? \1w",str);
  370. while(1) {
  371.     ch=getkey(K_UPPER);
  372.     if(ch=='Y' || ch==CR) {
  373.         bputs("Yes\r\n");
  374.         return(1); }
  375.     if(ch=='N' || aborted) {
  376.         bputs("No\r\n");
  377.         return(0); } }
  378. }
  379.  
  380. /****************************************************************************/
  381. /* Prompts user for N or Y (no or yes) and CR is interpreted as a N            */
  382. /* Returns 1 for N or 0 for Y                                                */
  383. /* Called from quite a few places                                            */
  384. /****************************************************************************/
  385. char noyes(char *str)
  386. {
  387.     char ch;
  388.  
  389. bprintf("\1_\1b\1h%s (y/N) ? \1w",str);
  390. while(1) {
  391.     ch=getkey(K_UPPER);
  392.     if(ch=='N' || ch==CR || aborted) {
  393.         bputs("No\r\n");
  394.         return(1); }
  395.     if(ch=='Y') {
  396.         bputs("Yes\r\n");
  397.         return(0); } }
  398. }
  399.  
  400. /****************************************************************************/
  401. /* Outputs a string highlighting characters preceeded by a tilde with the    */
  402. /* color specified in mnehigh and the rest of the line is in color mnelow.    */
  403. /* If the user doesn't have ANSI, it puts the character following the tilde */
  404. /* in parenthesis.                                                            */
  405. /****************************************************************************/
  406. void mnemonics(char *str)
  407. {
  408.     long l;
  409.  
  410. attr(mnelow);
  411. l=0L;
  412. while(str[l]) {
  413.     if(str[l]=='~' && str[l+1]) {
  414.         if(!(user_misc&ANSI))
  415.             outchar('(');
  416.         l++;
  417.         attr(mnehigh);
  418.         outchar(str[l]);
  419.         l++;
  420.         if(!(user_misc&ANSI))
  421.             outchar(')');
  422.         attr(mnelow); }
  423.     else
  424.         outchar(str[l++]); }
  425. attr(LIGHTGRAY);
  426. }
  427.  
  428. /****************************************************************************/
  429. /* If a key has been pressed, the ASCII code is returned. If not, 0 is        */
  430. /* returned. Ctrl-P and Ctrl-U are intercepted here.                        */
  431. /****************************************************************************/
  432. char inkey(int mode)
  433. {
  434.     static in_ctrl_p;
  435.     uchar ch=0;
  436.     uint i;
  437.  
  438. if(keybufbot!=keybuftop) {
  439.     ch=keybuf[keybufbot++];
  440.     if(keybufbot==KEY_BUFSIZE)
  441.         keybufbot=0; }
  442. else if(_bios_keybrd(1)) {
  443.     i=_bios_keybrd(0);
  444.     if(i&0xff)
  445.         ch=i&0xff;
  446.     else {                    /* Local Alt or Function key hit */
  447.         i>>=8;
  448.         switch(i) {
  449.             case 0x47:    /* Home - Same as Ctrl-B */
  450.                 return(2);    /* ctrl-b beginning of line */
  451.             case 0x4b:        /* Left Arrow - same as ctrl-] */
  452.                 return(0x1d);
  453.             case 0x4d:        /* Right Arrow - same as ctrl-f */
  454.                 return(6);
  455.             case 0x48:        /* Up arrow - same as ctrl-^ */
  456.                 return(0x1e);
  457.             case 0x50:        /* Down arrow - same as CR */
  458.                 return(CR);
  459.             case 0x4f:      /* End      - same as Ctrl-E */
  460.                 return(5);  /* ctrl-e - end of line */
  461.             case 0x52:    /* Insert */
  462.                 return(0x1f);    /* ctrl-minus - insert mode */
  463.             case 0x53:    /* Delete */
  464.                 return(0x7f);   /* ctrl-bkspc - del cur char */
  465.             }
  466.         return(0); } }
  467.  
  468. if(ch==0x10 || ch==0x1e) {    /* Ctrl-P or Ctrl-^ */
  469.     if(in_ctrl_p || !ctrl_dir[0])    /* keep from being recursive */
  470.         return(0);
  471.     in_ctrl_p=1;
  472.     SAVELINE;
  473.     CRLF;
  474.     nodemsg();
  475.     CRLF;
  476.     RESTORELINE;
  477.     lncntr=0;
  478.     in_ctrl_p=0;
  479.     return(0); }
  480.  
  481. if(ch==21) { /* Ctrl-U Users online */
  482.     if(!ctrl_dir[0])
  483.         return(0);
  484.     SAVELINE;
  485.     CRLF;
  486.     whos_online(1);
  487.     CRLF;
  488.     RESTORELINE;
  489.     lncntr=0;
  490.     return(0); }
  491.  
  492. if(ch==3)
  493.     aborted=1;
  494. else if(aborted)
  495.     ch=3;
  496.  
  497. if(!ch && (!(mode&K_GETSTR) || mode&K_LOWPRIO|| node_misc&NM_LOWPRIO))
  498.     mswait(0);
  499. return(ch);
  500. }
  501.  
  502. /****************************************************************************/
  503. /* Waits for remote or local user to hit a key. Inactivity timer is checked */
  504. /* and hangs up if inactive for 4 minutes. Returns key hit, or uppercase of */
  505. /* key hit if mode&K_UPPER or key out of KEY BUFFER. Does not print key.    */
  506. /****************************************************************************/
  507. char getkey(int mode)
  508. {
  509.     char ch,warn=0;
  510.     time_t timeout,now;
  511.  
  512. aborted=lncntr=0;
  513. timeout=time(NULL);
  514. do {
  515.     checkline();
  516.     ch=inkey(mode);
  517.     now=time(NULL);
  518.     if(ch) {
  519.         if(mode&K_NUMBER && isprint(ch) && !isdigit(ch))
  520.             continue;
  521.         if(mode&K_ALPHA && isprint(ch) && !isalpha(ch))
  522.             continue;
  523.         if(ch==LF) continue;
  524.         if(mode&K_UPPER)
  525.             return(toupper(ch));
  526.         return(ch); }
  527.     checktimeleft();
  528.     if(now-timeout>=sec_warn && !warn)     /* warning */
  529.         for(warn=0;warn<5;warn++)
  530.             outchar(7);
  531.     } while(now-timeout<sec_timeout);
  532. bputs("\r\nInactive too long.\r\n");
  533. exit(0);
  534. return(0);    /* never gets here, but makes compiler happy */
  535. }
  536.  
  537. /****************************************************************************/
  538. /* If remote user, checks DCD to see if user has hung up or not.            */
  539. /****************************************************************************/
  540. void checkline(void)
  541. {
  542. if(com_port && !((*msr)&DCD)) exit(0);
  543. }
  544.  
  545. /****************************************************************************/
  546. /* Waits for remote or local user to hit a key that is contained inside str.*/
  547. /* 'str' should contain uppercase characters only. When a valid key is hit, */
  548. /* it is echoed (upper case) and is the return value.                        */
  549. /* If max is non-zero and a number is hit that is not in str, it will be    */
  550. /* returned with the high bit set. If the return of this function has the    */
  551. /* high bit set (&0x8000), just flip the bit (^0x8000) to get the number.    */
  552. /****************************************************************************/
  553. int getkeys(char *str,int max)
  554. {
  555.     uchar ch,n=0;
  556.     int i=0;
  557.  
  558. strupr(str);
  559. while(!aborted) {
  560.     ch=getkey(K_UPPER);
  561.     if(max && ch>0x7f)    /* extended ascii chars are digits to isdigit() */
  562.         continue;
  563.     if(ch && !n && (strchr(str,ch))) {     /* return character if in string */
  564.         outchar(ch);
  565.         attr(LIGHTGRAY);
  566.         CRLF;
  567.         return(ch); }
  568.     if(ch==CR && max) {             /* return 0 if no number */
  569.         attr(LIGHTGRAY);
  570.         CRLF;
  571.         if(n)
  572.             return(i|0x8000);        /* return number plus high bit */
  573.         return(0); }
  574.     if(ch==BS && n) {
  575.         bputs("\b \b");
  576.         i/=10;
  577.         n--; }
  578.     else if(max && isdigit(ch) && (i*10)+(ch&0xf)<=max && (ch!='0' || n)) {
  579.         i*=10;
  580.         n++;
  581.         i+=ch&0xf;
  582.         outchar(ch);
  583.         if(i*10>max) {
  584.             attr(LIGHTGRAY);
  585.             CRLF;
  586.             return(i|0x8000); } } }
  587. return(0);
  588. }
  589.  
  590. /****************************************************************************/
  591. /* Hot keyed number input routine.                                            */
  592. /****************************************************************************/
  593. int getnum(int max)
  594. {
  595.     uchar ch,n=0;
  596.     int i=0;
  597.  
  598. while(1) {
  599.     ch=getkey(K_UPPER);
  600.     if(ch>0x7f)
  601.         continue;
  602.     if(ch=='Q') {
  603.         outchar('Q');
  604.         CRLF;
  605.         return(-1); }
  606.     else if(ch==3) {        /* ctrl-c */
  607.         CRLF;
  608.         return(-1); }
  609.     else if(ch==CR) {
  610.         CRLF;
  611.         return(i); }
  612.     else if(ch==BS && n) {
  613.         bputs("\b \b");
  614.         i/=10;
  615.         n--; }
  616.     else if(isdigit(ch) && (i*10)+(ch&0xf)<=max && (ch!='0' || n)) {
  617.         i*=10;
  618.         n++;
  619.         i+=ch&0xf;
  620.         outchar(ch);
  621.         if(i*10>max) {
  622.             CRLF;
  623.             return(i); } } }
  624. }
  625.  
  626. /****************************************************************************/
  627. /* Waits for remote or local user to input a CR terminated string. 'length' */
  628. /* is the maximum number of characters that getstr will allow the user to     */
  629. /* input into the string. 'mode' specifies upper case characters are echoed */
  630. /* or wordwrap or if in message input (^A sequences allowed). ^W backspaces */
  631. /* a word, ^X backspaces a line, ^Gs, BSs, TABs are processed, LFs ignored. */
  632. /* ^N non-destructive BS, ^V center line. Valid keys are echoed.            */
  633. /****************************************************************************/
  634. int getstr(char *strout, int maxlen, int mode)
  635. {
  636.     int i,l,x,z;    /* i=current position, l=length, j=printed chars */
  637.                     /* x&z=misc */
  638.     uchar ch,str1[256],str2[256],ins=0,atr;
  639.  
  640. if(mode&K_LINE && user_misc&ANSI) {
  641.     attr(LIGHTGRAY|HIGH|(BLUE<<4));  /* white on blue */
  642.     for(i=0;i<maxlen;i++)
  643.         outchar(SP);
  644.     bprintf("\x1b[%dD",maxlen); }
  645. i=l=0;    /* i=total number of chars, j=number of printable chars */
  646. if(wordwrap[0]) {
  647.     strcpy(str1,wordwrap);
  648.     wordwrap[0]=0; }
  649. else str1[0]=0;
  650. if(mode&K_EDIT)
  651.     strcat(str1,strout);
  652. if(strlen(str1)>maxlen)
  653.     str1[maxlen]=0;
  654. atr=curatr;
  655. if(mode&K_AUTODEL && str1[0])
  656.     attr(BLUE|(LIGHTGRAY<<4));
  657. rputs(str1);
  658. if(mode&K_EDIT && !(mode&(K_LINE|K_AUTODEL)) && user_misc&ANSI)
  659.     bputs("\x1b[K");  /* destroy to eol */
  660. i=l=strlen(str1);
  661.  
  662. if(mode&K_AUTODEL && str1[0]) {
  663.     ch=getkey(mode);
  664.     attr(atr);
  665.     if(isprint(ch) || ch==0x7f) {
  666.         for(i=0;i<l;i++)
  667.             bputs("\b \b");
  668.         i=l=0; }
  669.     else {
  670.         for(i=0;i<l;i++)
  671.             outchar(BS);
  672.         rputs(str1);
  673.         i=l; }
  674.     if(ch!=SP && ch!=TAB)
  675.         ungetkey(ch); }
  676.  
  677. while((ch=getkey(mode|K_GETSTR))!=CR && !aborted) {
  678.     switch(ch) {
  679.         case 1:    /* Ctrl-A for ANSI */
  680.             if(!(mode&K_MSG) || i>maxlen-3)
  681.                 break;
  682.             if(ins) {
  683.                 if(l<maxlen)
  684.                     l++;
  685.                 for(x=l;x>i;x--)
  686.                     str1[x]=str1[x-1];
  687.                 rprintf("%.*s",l-i,str1+i);
  688.                 rprintf("\x1b[%dD",l-i);
  689.                 if(i==maxlen-1)
  690.                     ins=0; }
  691.             outchar(str1[i++]=1);
  692.             break;
  693.         case 2:    /* Ctrl-B Beginning of Line */
  694.             if(user_misc&ANSI && i) {
  695.                 bprintf("\x1b[%dD",i);
  696.                 i=0; }
  697.             break;
  698.         case 4:    /* Ctrl-D Delete word right */
  699.             if(i<l) {
  700.                 x=i;
  701.                 while(x<l && str1[x]!=SP) {
  702.                     outchar(SP);
  703.                     x++; }
  704.                 while(x<l && str1[x]==SP) {
  705.                     outchar(SP);
  706.                     x++; }
  707.                 bprintf("\x1b[%dD",x-i);   /* move cursor back */
  708.                 z=i;
  709.                 while(z<l-(x-i))  {             /* move chars in string */
  710.                     outchar(str1[z]=str1[z+(x-i)]);
  711.                     z++; }
  712.                 while(z<l) {                    /* write over extra chars */
  713.                     outchar(SP);
  714.                     z++; }
  715.                 bprintf("\x1b[%dD",z-i);
  716.                 l-=x-i; }                        /* l=new length */
  717.             break;
  718.         case 5:    /* Ctrl-E End of line */
  719.             if(user_misc&ANSI && i<l) {
  720.                 bprintf("\x1b[%dC",l-i);  /* move cursor right one */
  721.                 i=l; }
  722.             break;
  723.         case 6:    /* Ctrl-F move cursor forewards */
  724.             if(i<l && (user_misc&ANSI)) {
  725.                 bputs("\x1b[C");   /* move cursor right one */
  726.                 i++; }
  727.             break;
  728.         case 7:
  729.             if(!(mode&K_MSG))
  730.                 break;
  731.              if(ins) {
  732.                 if(l<maxlen)
  733.                     l++;
  734.                 for(x=l;x>i;x--)
  735.                     str1[x]=str1[x-1];
  736.                 if(i==maxlen-1)
  737.                     ins=0; }
  738.              if(i<maxlen) {
  739.                 str1[i++]=7;
  740.                 outchar(7); }
  741.              break;
  742.         case 14:    /* Ctrl-N Next word */
  743.             if(i<l && (user_misc&ANSI)) {
  744.                 x=i;
  745.                 while(str1[i]!=SP && i<l)
  746.                     i++;
  747.                 while(str1[i]==SP && i<l)
  748.                     i++;
  749.                 bprintf("\x1b[%dC",i-x); }
  750.             break;
  751.         case 0x1c:      /* Ctrl-\ Previous word */
  752.             if(i && (user_misc&ANSI)) {
  753.                 x=i;
  754.                 while(str1[i-1]==SP && i)
  755.                     i--;
  756.                 while(str1[i-1]!=SP && i)
  757.                     i--;
  758.                 bprintf("\x1b[%dD",x-i); }
  759.             break;
  760.         case 18:    /* Ctrl-R Redraw Line */
  761.             redrwstr(str1,i,l,0);
  762.             break;
  763.         case TAB:
  764.             if(!(i%TABSIZE)) {
  765.                 if(ins) {
  766.                     if(l<maxlen)
  767.                         l++;
  768.                     for(x=l;x>i;x--)
  769.                         str1[x]=str1[x-1];
  770.                     if(i==maxlen-1)
  771.                         ins=0; }
  772.                 str1[i++]=SP;
  773.                 outchar(SP); }
  774.             while(i<maxlen && i%TABSIZE) {
  775.                 if(ins) {
  776.                     if(l<maxlen)
  777.                         l++;
  778.                     for(x=l;x>i;x--)
  779.                         str1[x]=str1[x-1];
  780.                     if(i==maxlen-1)
  781.                         ins=0; }
  782.                 str1[i++]=SP;
  783.                 outchar(SP); }
  784.             if(ins)
  785.                 redrwstr(str1,i,l,0);
  786.             break;
  787.         case BS:
  788.             if(!i)
  789.                 break;
  790.             i--;
  791.             l--;
  792.             if(i!=l) {                /* Deleting char in middle of line */
  793.                 outchar(BS);
  794.                 z=i;
  795.                 while(z<l)    {        /* move the characters in the line */
  796.                     outchar(str1[z]=str1[z+1]);
  797.                     z++; }
  798.                 outchar(SP);        /* write over the last char */
  799.                 bprintf("\x1b[%dD",(l-i)+1); }
  800.             else
  801.                 bputs("\b \b");
  802.             break;
  803.         case 22:    /* Ctrl-V     Center line */
  804.             str1[l]=0;
  805.             l=bstrlen(str1);
  806.             for(x=0;x<(maxlen-l)/2;x++)
  807.                 str2[x]=SP;
  808.             str2[x]=0;
  809.             strcat(str2,str1);
  810.             strcpy(strout,str2);
  811.             l=strlen(strout);
  812.             if(mode&K_MSG)
  813.                 redrwstr(strout,i,l,K_MSG);
  814.             else {
  815.                 while(i--)
  816.                     bputs("\b");
  817.                 bputs(strout);
  818.                 if(mode&K_LINE)
  819.                     attr(LIGHTGRAY); }
  820.             CRLF;
  821.             return(l);
  822.         case 23:    /* Ctrl-W   Delete word left */
  823.             if(i<l) {
  824.                 x=i;                            /* x=original offset */
  825.                 while(i && str1[i-1]==SP) {
  826.                     outchar(BS);
  827.                     i--; }
  828.                 while(i && str1[i-1]!=SP) {
  829.                     outchar(BS);
  830.                     i--; }
  831.                 z=i;                            /* i=z=new offset */
  832.                 while(z<l-(x-i))  {             /* move chars in string */
  833.                     outchar(str1[z]=str1[z+(x-i)]);
  834.                     z++; }
  835.                 while(z<l) {                    /* write over extra chars */
  836.                     outchar(SP);
  837.                     z++; }
  838.                 bprintf("\x1b[%dD",z-i);        /* back to new x corridnant */
  839.                 l-=x-i; }                        /* l=new length */
  840.             else {
  841.                 while(i && str1[i-1]==SP) {
  842.                     i--;
  843.                     l--;
  844.                     bputs("\b \b"); }
  845.                 while(i && str1[i-1]!=SP) {
  846.                     i--;
  847.                     l--;
  848.                     bputs("\b \b"); } }
  849.             break;
  850.         case 24:    /* Ctrl-X   Delete entire line */
  851.             while(i<l) {
  852.                 outchar(SP);
  853.                 i++; }
  854.             while(l) {
  855.                 l--;
  856.                 bputs("\b \b"); }
  857.             i=0;
  858.             break;
  859.         case 25:    /* Ctrl-Y    Delete to end of line */
  860.             if(user_misc&ANSI) {
  861.                 bputs("\x1b[s\x1b[K\x1b[u");
  862.                 l=i; }
  863.             break;
  864.         case 31:    /* Ctrl-Minus        Toggles Insert/Overwrite */
  865.             if(!(user_misc&ANSI))
  866.                 break;
  867.             if(ins) {
  868.                 ins=0;
  869.                 redrwstr(str1,i,l,0); }
  870.             else if(i<l) {
  871.                 ins=1;
  872.                 bprintf("\x1b[s\x1b[%dC",80-i);         /* save pos  */
  873.                 z=curatr;                                /* and got to EOL */
  874.                 attr(z|BLINK|HIGH);
  875.                 outchar('░');
  876.                 attr(z);
  877.                 bputs("\x1b[u"); }  /* restore pos */
  878.             break;
  879.         case 0x1d:    /* Ctrl-]  Reverse Cursor Movement */
  880.             if(i && (user_misc&ANSI)) {
  881.                 bputs("\x1b[D");   /* move cursor left one */
  882.                 i--; }
  883.             break;
  884.         case 0x7f:    /* Ctrl-BkSpc (DEL) Delete current char */
  885.             if(i==l)
  886.                 break;
  887.             l--;
  888.             z=i;
  889.             while(z<l)    {        /* move the characters in the line */
  890.                 outchar(str1[z]=str1[z+1]);
  891.                 z++; }
  892.             outchar(SP);        /* write over the last char */
  893.             bprintf("\x1b[%dD",(l-i)+1);
  894.             break;
  895.         case ESC:
  896.             if(!(user_misc&ANSI))
  897.                 break;
  898.             if((ch=getkey(0x8000))!='[') {
  899.                 ungetch(ch);
  900.                 break; }
  901.             if((ch=getkey(0x8000))=='C') {
  902.                 if(i<l) {
  903.                     bputs("\x1b[C");   /* move cursor right one */
  904.                     i++; } }
  905.             else if(ch=='D') {
  906.                 if(i) {
  907.                     bputs("\x1b[D");   /* move cursor left one */
  908.                     i--; } }
  909.             else {
  910.                 while(isdigit(ch) || ch==';' || isalpha(ch)) {
  911.                     if(isalpha(ch)) {
  912.                         ch=getkey(0);
  913.                         break; }
  914.                     ch=getkey(0); }
  915.                 ungetch(ch); }
  916.             break;
  917.         default:
  918.             if(mode&K_WRAP && i==maxlen && ch>=SP && !ins) {
  919.                 str1[i]=0;
  920.                 if(ch==SP) {    /* don't wrap a space as last char */
  921.                     strcpy(strout,str1);
  922.                     if(stripattr(strout))
  923.                         redrwstr(strout,i,l,K_MSG);
  924.                     CRLF;
  925.                     return(i); }
  926.                 x=i-1;
  927.                 z=1;
  928.                 wordwrap[0]=ch;
  929.                 while(str1[x]!=SP && x)
  930.                     wordwrap[z++]=str1[x--];
  931.                 if(x<(maxlen/2)) {
  932.                     wordwrap[1]=0;    /* only wrap one character */
  933.                     strcpy(strout,str1);
  934.                     if(stripattr(strout))
  935.                         redrwstr(strout,i,l,K_MSG);
  936.                     CRLF;
  937.                     return(i); }
  938.                 wordwrap[z]=0;
  939.                 while(z--) {
  940.                     i--;
  941.                     bputs("\b \b"); }
  942.                 strrev(wordwrap);
  943.                 str1[x]=0;
  944.                 strcpy(strout,str1);
  945.                 if(stripattr(strout))
  946.                     redrwstr(strout,i,x,mode);
  947.                 CRLF;
  948.                 return(x); }
  949.             if(i<maxlen && ch>=SP) {
  950.                 if(mode&K_UPRLWR)
  951.                     if(!i || (i && (str1[i-1]==SP || str1[i-1]=='-'
  952.                         || str1[i-1]=='.' || str1[i-1]=='_')))
  953.                         ch=toupper(ch);
  954.                     else
  955.                         ch=tolower(ch);
  956.                 if(ins) {
  957.                     if(l<maxlen)    /* l<maxlen */
  958.                         l++;
  959.                     for(x=l;x>i;x--)
  960.                         str1[x]=str1[x-1];
  961.                     rprintf("%.*s",l-i,str1+i);
  962.                     rprintf("\x1b[%dD",l-i);
  963.                     if(i==maxlen-1) {
  964.                         bputs("  \b\b");
  965.                         ins=0; } }
  966.                 str1[i++]=ch;
  967.                 outchar(ch); } }
  968.     if(i>l)
  969.         l=i;
  970.     if(mode&K_CHAT && !l)
  971.         return(0); }
  972. if(i>l)
  973.     l=i;
  974. str1[l]=0;
  975. if(!aborted) {
  976.     strcpy(strout,str1);
  977.     if(stripattr(strout) || ins)
  978.         redrwstr(strout,i,l,K_MSG); }
  979. else
  980.     l=0;
  981. if(mode&K_LINE) attr(LIGHTGRAY);
  982. if(!(mode&K_NOCRLF)) {
  983.     outchar(CR);
  984.     if(!(mode&K_MSG && aborted))
  985.         outchar(LF); }
  986. return(l);
  987. }
  988.  
  989. /****************************************************************************/
  990. /* Redraws str using i as current cursor position and l as length           */
  991. /****************************************************************************/
  992. void redrwstr(char *strin, int i, int l, char mode)
  993. {
  994.     char str[256],c;
  995.  
  996. sprintf(str,"%-*.*s",l,l,strin);
  997. c=i;
  998. while(c--)
  999.     outchar(BS);
  1000. if(mode&K_MSG)
  1001.     bputs(str);
  1002. else
  1003.     rputs(str);
  1004. if(user_misc&ANSI) {
  1005.     bputs("\x1b[K");
  1006.     if(i<l)
  1007.         bprintf("\x1b[%dD",l-i); }
  1008. else {
  1009.     while(c<79)    { /* clear to end of line */
  1010.         outchar(SP);
  1011.         c++; }
  1012.     while(c>l) { /* back space to end of string */
  1013.         outchar(BS);
  1014.         c--; } }
  1015. }
  1016.  
  1017. /****************************************************************************/
  1018. /* Strips invalid Ctrl-Ax sequences from str                                */
  1019. /* Returns number of ^A's in line                                           */
  1020. /****************************************************************************/
  1021. char stripattr(char *strin)
  1022. {
  1023.     uchar str[81];
  1024.     uchar a,c,d,e;
  1025.  
  1026. e=strlen(strin);
  1027. for(a=c=d=0;c<e;c++) {
  1028.     if(strin[c]==1) {
  1029.         a++;
  1030.         switch(toupper(strin[c+1])) {
  1031.             case '-':    /* clear         */
  1032.             case '_':    /* clear        */
  1033.             case 'B':    /* blue     fg     */
  1034.             case 'C':    /* cyan     fg     */
  1035.             case 'G':    /* green    fg     */
  1036.             case 'H':    /* high     fg     */
  1037.             case 'I':    /* blink            */
  1038.             case 'K':    /* black     fg     */
  1039.             case 'L':    /* cls             */
  1040.             case 'M':    /* magenta  fg     */
  1041.             case 'N':    /* normal          */
  1042.             case 'P':    /* pause           */
  1043.             case 'Q':   /* pause reset  */
  1044.             case 'R':    /* red      fg     */
  1045.             case 'W':    /* white    fg     */
  1046.             case 'Y':    /* yellow   fg     */
  1047.             case '0':    /* black     bg     */
  1048.             case '1':    /* red       bg     */
  1049.             case '2':    /* green     bg     */
  1050.             case '3':   /* brown    bg     */
  1051.             case '4':    /* blue      bg     */
  1052.             case '5':   /* magenta     bg     */
  1053.             case '6':    /* cyan        bg     */
  1054.             case '7':    /* white       bg     */
  1055.                 break;
  1056.             default:
  1057.                 c++;
  1058.                 continue; } }
  1059.     str[d++]=strin[c]; }
  1060. str[d]=0;
  1061. strcpy(strin,str);
  1062. return(a);
  1063. }
  1064.  
  1065. /***************************************************************************/
  1066. /* Changes local and remote text attributes accounting for monochrome      */
  1067. /***************************************************************************/
  1068. void attr(char atr)
  1069. {
  1070.  
  1071. if(!(user_misc&ANSI) || aborted)
  1072.     return;
  1073. if(!(user_misc&COLOR)) {  /* eliminate colors if user doesn't have them */
  1074.     if(atr&LIGHTGRAY)        /* if any bits set, set all */
  1075.         atr|=LIGHTGRAY;
  1076.     if(atr&(LIGHTGRAY<<4))
  1077.         atr|=(LIGHTGRAY<<4);
  1078.     if(atr&LIGHTGRAY && atr&(LIGHTGRAY<<4))
  1079.         atr&=~LIGHTGRAY; }    /* if background is solid, forground is black */
  1080. if(curatr==atr) /* attribute hasn't changed. don't send codes */
  1081.     return;
  1082.  
  1083. if((!(atr&HIGH) && curatr&HIGH)    || (!(atr&BLINK) && curatr&BLINK)
  1084.     || atr==LIGHTGRAY) {
  1085.     bprintf("\x1b[0m");
  1086.     curatr=LIGHTGRAY; }
  1087.  
  1088. if(atr==LIGHTGRAY) {                 /* no attributes */
  1089.     curatr=atr;
  1090.     return; }
  1091.  
  1092. if(atr&BLINK) {                        /* special attributes */
  1093.     if(!(curatr&BLINK))
  1094.         bprintf("\x1b[5m"); }
  1095. if(atr&HIGH) {
  1096.     if(!(curatr&HIGH))
  1097.         bprintf("\x1b[1m"); }
  1098.  
  1099. if((atr&0x7)==BLACK) {                /* foreground colors */
  1100.     if((curatr&0x7)!=BLACK)
  1101.         bprintf("\x1b[30m"); }
  1102. else if((atr&0x7)==RED) {
  1103.     if((curatr&0x7)!=RED)
  1104.         bprintf("\x1b[31m"); }
  1105. else if((atr&0x7)==GREEN) {
  1106.     if((curatr&0x7)!=GREEN)
  1107.         bprintf("\x1b[32m"); }
  1108. else if((atr&0x7)==BROWN) {
  1109.     if((curatr&0x7)!=BROWN)
  1110.         bprintf("\x1b[33m"); }
  1111. else if((atr&0x7)==BLUE) {
  1112.     if((curatr&0x7)!=BLUE)
  1113.         bprintf("\x1b[34m"); }
  1114. else if((atr&0x7)==MAGENTA) {
  1115.     if((curatr&0x7)!=MAGENTA)
  1116.         bprintf("\x1b[35m"); }
  1117. else if((atr&0x7)==CYAN) {
  1118.     if((curatr&0x7)!=CYAN)
  1119.         bprintf("\x1b[36m"); }
  1120. else if((atr&0x7)==LIGHTGRAY) {
  1121.     if((curatr&0x7)!=LIGHTGRAY)
  1122.         bprintf("\x1b[37m"); }
  1123.  
  1124. if((atr&0x70)==(BLACK<<4)) {        /* background colors */
  1125.     if((curatr&0x70)!=(BLACK<<4))
  1126.         bprintf("\x1b[40m"); }
  1127. else if((atr&0x70)==(RED<<4)) {
  1128.     if((curatr&0x70)!=(RED<<4))
  1129.         bprintf("\x1b[41m"); }
  1130. else if((atr&0x70)==(GREEN<<4)) {
  1131.     if((curatr&0x70)!=(GREEN<<4))
  1132.         bprintf("\x1b[42m"); }
  1133. else if((atr&0x70)==(BROWN<<4)) {
  1134.     if((curatr&0x70)!=(BROWN<<4))
  1135.         bprintf("\x1b[43m"); }
  1136. else if((atr&0x70)==(BLUE<<4)) {
  1137.     if((curatr&0x70)!=(BLUE<<4))
  1138.         bprintf("\x1b[44m"); }
  1139. else if((atr&0x70)==(MAGENTA<<4)) {
  1140.     if((curatr&0x70)!=(MAGENTA<<4))
  1141.         bprintf("\x1b[45m"); }
  1142. else if((atr&0x70)==(CYAN<<4)) {
  1143.     if((curatr&0x70)!=(CYAN<<4))
  1144.         bprintf("\x1b[46m"); }
  1145. else if((atr&0x70)==(LIGHTGRAY<<4)) {
  1146.     if((curatr&0x70)!=(LIGHTGRAY<<4))
  1147.         bprintf("\x1b[47m"); }
  1148.  
  1149. curatr=atr;
  1150. }
  1151.  
  1152. /****************************************************************************/
  1153. /* Peform clear screen                                                        */
  1154. /****************************************************************************/
  1155. void cls(void)
  1156. {
  1157.     int i;
  1158.  
  1159. if(lncntr>1 && !tos) {
  1160.     lncntr=0;
  1161.     CRLF;
  1162.     pause();
  1163.     while(lncntr && !aborted)
  1164.         pause(); }
  1165.  
  1166. if(user_misc&ANSI)
  1167.     bprintf("\x1b[2J");
  1168. else {
  1169.     outchar(FF);
  1170.     clrscr(); }
  1171. tos=1;
  1172. lncntr=0;
  1173. }
  1174.  
  1175. #ifdef __WATCOMC__
  1176.  
  1177. short wherey(void)
  1178. {
  1179.     struct rccoord rc;
  1180.  
  1181. rc=_gettextposition();
  1182. return(rc.col);
  1183. }
  1184.  
  1185. void clrscr(void)
  1186. {
  1187. _clearscreen(_GCLEARSCREEN);
  1188. }
  1189.  
  1190. #endif
  1191.  
  1192. /****************************************************************************/
  1193. /* performs the correct attribute modifications for the Ctrl-A code            */
  1194. /****************************************************************************/
  1195. void ctrl_a(char x)
  1196. {
  1197.     char atr=curatr;
  1198.     int i,j;
  1199.  
  1200. if((uchar)x>=0x7f) {
  1201.     if(user_misc&ANSI)
  1202.         bprintf("\x1b[%uC",(uchar)x-0x7f);
  1203.     else
  1204.         for(i=0;i<(uchar)x-0x7f;i++)
  1205.             outchar(SP);
  1206.     return; }
  1207.  
  1208. switch(toupper(x)) {
  1209.     case '-':                                /* turn off all attributes if */
  1210.         if(atr&(HIGH|BLINK|(LIGHTGRAY<<4)))    /* high intensity, blink or */
  1211.             attr(LIGHTGRAY);                /* background bits are set */
  1212.         break;
  1213.     case '_':                                /* turn off all attributes if */
  1214.         if(atr&(BLINK|(LIGHTGRAY<<4)))        /* blink or background is set */
  1215.             attr(LIGHTGRAY);
  1216.         break;
  1217.     case ',':   /* Delay 1/10 sec */
  1218.         mswait(100);
  1219.         break;
  1220.     case ';':   /* Delay 1/2 sec */
  1221.         mswait(500);
  1222.         break;
  1223.     case '.':   /* Delay 2 secs */
  1224.         mswait(2000);
  1225.         break;
  1226.     case 'P':    /* Pause */
  1227.         pause();
  1228.         break;
  1229.     case 'Q':   /* Pause reset */
  1230.         lncntr=0;
  1231.         break;
  1232.     case 'L':    /* CLS (form feed) */
  1233.         cls();
  1234.         break;
  1235.     case '>':   /* CLREOL */
  1236.         if(user_misc&ANSI)
  1237.             bputs("\x1b[K");
  1238.         else {
  1239.             i=j=wherey();
  1240.             while(i++<80)
  1241.                 outchar(SP);
  1242.             while(j++<80)
  1243.                 outchar(BS); }
  1244.         break;
  1245.     case '<':   /* Non-destructive backspace */
  1246.         outchar(BS);
  1247.         break;
  1248.     case '[':   /* Carriage return */
  1249.         outchar(CR);
  1250.         break;
  1251.     case ']':   /* Line feed */
  1252.         outchar(LF);
  1253.         break;
  1254.     case 'A':   /* Ctrl-A */
  1255.         outchar(1);
  1256.         break;
  1257.     case 'H':     /* High intensity */
  1258.         atr|=HIGH;
  1259.         attr(atr);
  1260.         break;
  1261.     case 'I':    /* Blink */
  1262.         atr|=BLINK;
  1263.         attr(atr);
  1264.         break;
  1265.     case 'N':     /* Normal */
  1266.         attr(LIGHTGRAY);
  1267.         break;
  1268.     case 'R':
  1269.         atr=(atr&0xf8)|RED;
  1270.         attr(atr);
  1271.         break;
  1272.     case 'S':
  1273.         nodesync();
  1274.         break;
  1275.     case 'G':
  1276.         atr=(atr&0xf8)|GREEN;
  1277.         attr(atr);
  1278.         break;
  1279.     case 'B':
  1280.         atr=(atr&0xf8)|BLUE;
  1281.         attr(atr);
  1282.         break;
  1283.     case 'W':    /* White */
  1284.         atr=(atr&0xf8)|LIGHTGRAY;
  1285.         attr(atr);
  1286.         break;
  1287.     case 'C':
  1288.         atr=(atr&0xf8)|CYAN;
  1289.         attr(atr);
  1290.         break;
  1291.     case 'M':
  1292.         atr=(atr&0xf8)|MAGENTA;
  1293.         attr(atr);
  1294.         break;
  1295.     case 'Y':
  1296.         atr=(atr&0xf8)|BROWN;
  1297.         attr(atr);
  1298.         break;
  1299.     case 'K':    /* Black */
  1300.         atr=(atr&0xf8)|BLACK;
  1301.         attr(atr);
  1302.         break;
  1303.     case '0':    /* Black Background */
  1304.         atr=(atr&0x8f)|(BLACK<<4);
  1305.         attr(atr);
  1306.         break;
  1307.     case '1':    /* Red Background */
  1308.         atr=(atr&0x8f)|(RED<<4);
  1309.         attr(atr);
  1310.         break;
  1311.     case '2':    /* Green Background */
  1312.         atr=(atr&0x8f)|(GREEN<<4);
  1313.         attr(atr);
  1314.         break;
  1315.     case '3':    /* Yellow Background */
  1316.         atr=(atr&0x8f)|(BROWN<<4);
  1317.         attr(atr);
  1318.         break;
  1319.     case '4':    /* Blue Background */
  1320.         atr=(atr&0x8f)|(BLUE<<4);
  1321.         attr(atr);
  1322.         break;
  1323.     case '5':    /* Magenta Background */
  1324.         atr=(atr&0x8f)|(MAGENTA<<4);
  1325.         attr(atr);
  1326.         break;
  1327.     case '6':    /* Cyan Background */
  1328.         atr=(atr&0x8f)|(CYAN<<4);
  1329.         attr(atr);
  1330.         break;
  1331.     case '7':    /* White Background */
  1332.         atr=(atr&0x8f)|(LIGHTGRAY<<4);
  1333.         attr(atr);
  1334.         break; }
  1335. }
  1336.  
  1337. /****************************************************************************/
  1338. /* Network open function. Opens all files DENYALL and retries LOOP_NOPEN    */
  1339. /* number of times if the attempted file is already open or denying access  */
  1340. /* for some other reason.    All files are opened in BINARY mode.            */
  1341. /****************************************************************************/
  1342. int nopen(char *str, int access)
  1343. {
  1344.     char count=0;
  1345.     int file,share;
  1346.  
  1347. if(access&SH_DENYNO) share=SH_DENYNO;
  1348. else if(access==O_RDONLY) share=SH_DENYWR;
  1349. else share=SH_DENYRW;
  1350. while(((file=sopen(str,O_BINARY|access,share,S_IWRITE))==-1)
  1351.     && errno==EACCES && count++<LOOP_NOPEN)
  1352.     if(count>10)
  1353.         mswait(50);
  1354. if(count>(LOOP_NOPEN/2) && count<=LOOP_NOPEN)
  1355.     bprintf("\r\nNOPEN COLLISION - File: %s Count: %d\r\n"
  1356.         ,str,count);
  1357. if(file==-1 && errno==EACCES)
  1358.     bputs("\7\r\nNOPEN: ACCESS DENIED\r\n\7");
  1359. return(file);
  1360. }
  1361.  
  1362. /****************************************************************************/
  1363. /* Reads data from XTRN.DAT in the node directory and fills the appropriate */
  1364. /* global variables.                                                        */
  1365. /* Initializes starttime variable with current time.                        */
  1366. /****************************************************************************/
  1367. void initdata(void)
  1368. {
  1369.     char str[256],tmp[256];
  1370.     int i;
  1371.     FILE *stream;
  1372.  
  1373. #if defined(__TURBOC__) || defined(__SC__)    /* Borland or Symantec */
  1374.     ctrlbrk(cbreakh);
  1375. #endif
  1376.  
  1377. #ifdef __WATCOMC__
  1378.     putenv("TZ=UCT0");
  1379.     setvbuf(stdout,NULL,_IONBF,0);
  1380.     setvbuf(stderr,NULL,_IONBF,0);
  1381. #endif
  1382.  
  1383. #ifdef __SC__
  1384.     setvbuf(stdout,NULL,_IONBF,0);
  1385.     con_fp=stdout;
  1386. #else
  1387.     con_fp=stderr;
  1388. #endif
  1389.  
  1390. if(setmode(fileno(con_fp),O_BINARY)==-1) {     /* eliminate LF expansion */
  1391.     printf("Can't set console output to BINARY\n");
  1392.     exit(1); }
  1393.  
  1394. sprintf(str,"%sXTRN.DAT",node_dir);
  1395. if((stream=fopen(str,"rt"))==NULL) {
  1396.     printf("Can't open %s\r\n",str);
  1397.     exit(1); }
  1398. fgets(str,81,stream);            /* username */
  1399. sprintf(user_name,"%.25s",str);
  1400. truncsp(user_name);
  1401. fgets(str,81,stream);            /* system name */
  1402. sprintf(sys_name,"%.40s",str);
  1403. truncsp(sys_name);
  1404. fgets(str,81,stream);            /* system operator */
  1405. sprintf(sys_op,"%.40s",str);
  1406. truncsp(sys_op);
  1407. fgets(str,81,stream);            /* system guru */
  1408. sprintf(sys_guru,"%.40s",str);
  1409. truncsp(sys_guru);
  1410.  
  1411. fgets(str,81,stream);            /* ctrl dir */
  1412. str[50]=0;
  1413. if(str[0]=='.')
  1414.     sprintf(ctrl_dir,"%s%s",node_dir,str);
  1415. else
  1416.     strcpy(ctrl_dir,str);
  1417. truncsp(ctrl_dir);
  1418. if(_fullpath(str,ctrl_dir,50))
  1419.     strcpy(ctrl_dir,str);
  1420. backslash(ctrl_dir);
  1421.  
  1422. fgets(str,81,stream);            /* data dir */
  1423. if(str[0]=='.')
  1424.     sprintf(data_dir,"%s%s",node_dir,str);
  1425. else
  1426.     sprintf(data_dir,"%.40s",str);
  1427. truncsp(data_dir);
  1428. if(_fullpath(str,data_dir,50))
  1429.     strcpy(data_dir,str);
  1430. backslash(data_dir);
  1431.  
  1432. fgets(str,81,stream);            /* total nodes */
  1433. sys_nodes=atoi(str);
  1434. fgets(str,81,stream);            /* current node */
  1435. node_num=atoi(str);
  1436. fgets(str,81,stream);            /* time left */
  1437. timeleft=atoi(str);
  1438. fgets(str,81,stream);            /* ANSI? (Yes, Mono, or No) */
  1439. user_misc=0;
  1440. if(str[0]=='Y')
  1441.     user_misc|=(ANSI|COLOR);
  1442. else if(str[0]=='M')
  1443.     user_misc|=ANSI;
  1444. fgets(str,81,stream);            /* screen lines */
  1445. user_rows=atoi(str);
  1446. fgets(str,81,stream);            /* credits */
  1447. user_cdt=atol(str);
  1448. fgets(str,81,stream);            /* level */
  1449. user_level=atoi(str);
  1450. fgets(str,81,stream);            /* was transfer level, left for compat. */
  1451. fgets(str,81,stream);            /* birthdate */
  1452. truncsp(str);
  1453. sprintf(user_birth,"%.8s",str);
  1454. fgets(str,81,stream);            /* sex */
  1455. user_sex=str[0];
  1456. fgets(str,81,stream);            /* user number */
  1457. user_number=atoi(str);
  1458. fgets(str,81,stream);            /* user phone number */
  1459. sprintf(user_phone,"%.12s",str);
  1460. truncsp(user_phone);
  1461. fgets(str,81,stream);            /* com port (0 if local or no modem) */
  1462. com_port=atoi(str);
  1463. fgets(str,81,stream);            /* com (UART) irq */
  1464. com_irq=atoi(str);
  1465. fgets(str,81,stream);            /* com (UART) base address in hex */
  1466. truncsp(str);
  1467. com_base=(uint)ahtoul(str);
  1468. fgets(str,81,stream);            /* com rate */
  1469. com_rate=(ulong)atol(str);
  1470. fgets(str,81,stream);            /* hardware flow control (Y/N) */
  1471. if(toupper(str[0])=='Y')
  1472.     mdm_misc|=MDM_FLOWCTRL;
  1473. fgets(str,81,stream);            /* locked DTE rate (Y/N) */
  1474. if(toupper(str[0])=='Y')
  1475.     mdm_misc|=MDM_STAYHIGH;
  1476. fgets(str,81,stream);            /* modem initialization string */
  1477. sprintf(mdm_init,"%.63s",str);
  1478. truncsp(mdm_init);
  1479. fgets(str,81,stream);            /* modem special init string */
  1480. sprintf(mdm_spec,"%.63s",str);
  1481. truncsp(mdm_spec);
  1482. fgets(str,81,stream);            /* modem terminal mode string */
  1483. sprintf(mdm_term,"%.63s",str);
  1484. truncsp(mdm_term);
  1485. fgets(str,81,stream);            /* modem dial string */
  1486. sprintf(mdm_dial,"%.63s",str);
  1487. truncsp(mdm_dial);
  1488. fgets(str,81,stream);            /* modem off-hook string */
  1489. sprintf(mdm_offh,"%.63s",str);
  1490. truncsp(mdm_offh);
  1491. fgets(str,81,stream);            /* modem answer string */
  1492. sprintf(mdm_answ,"%.63s",str);
  1493. truncsp(mdm_answ);
  1494. fgets(str,81,stream);            /* memory address of modem status register */
  1495. msr=(uint far *)atol(str);
  1496. if(!fgets(str,81,stream))        /* total number of external programs */
  1497.     total_xtrns=0;
  1498. else
  1499.     total_xtrns=atoi(str);
  1500. if(total_xtrns && (xtrn=(char **)MALLOC(sizeof(char *)*total_xtrns))==NULL) {
  1501.     printf("Allocation error 1: %u\r\n",sizeof(char *)*total_xtrns);
  1502.     exit(1); }
  1503. for(i=0;i<total_xtrns;i++) {
  1504.     fgets(str,81,stream);
  1505.     truncsp(str);
  1506.     if((xtrn[i]=(char *)MALLOC(strlen(str)+1))==NULL) {
  1507.         printf("Allocation error 2 (%u): %u\r\n",i,strlen(str)+1);
  1508.         exit(1); }
  1509.     strcpy(xtrn[i],str); }
  1510. fgets(str,81,stream);            /* user's main flags */
  1511. sprintf(user_flags1,"%.26s",str);
  1512. fgets(str,81,stream);            /* user's xfer flags */
  1513. sprintf(user_flags2,"%.26s",str);
  1514. fgets(str,81,stream);            /* user's exemptions */
  1515. sprintf(user_exempt,"%.26s",str);
  1516. fgets(str,81,stream);            /* user's restrictions */
  1517. sprintf(user_rest,"%.26s",str);
  1518. fgets(str,81,stream);            /* user's expiration date */
  1519. truncsp(str);
  1520. user_expire=ahtoul(str);
  1521. str[0]=0;
  1522. fgets(str,81,stream);            /* user's address */
  1523. sprintf(user_address,"%.30s",str);
  1524. truncsp(user_address);
  1525. fgets(str,81,stream);            /* user's location (city, state) */
  1526. sprintf(user_location,"%.30s",str);
  1527. truncsp(user_location);
  1528. fgets(str,81,stream);            /* user's zip/postal code */
  1529. sprintf(user_zipcode,"%.10s",str);
  1530. truncsp(user_zipcode);
  1531. str[0]=0;
  1532. fgets(str,81,stream);
  1533. sprintf(user_flags3,"%.26s",str);
  1534. fgets(str,81,stream);
  1535. sprintf(user_flags4,"%.26s",str);
  1536. if(fgets(str,81,stream))        /* Time-slice API type */
  1537.     mswtyp=ahtoul(str);
  1538. str[0]=0;
  1539. fgets(str,81,stream);
  1540. truncsp(str);
  1541. sprintf(user_realname,"%.25s",str);
  1542. str[0]=0;
  1543. fgets(str,81,stream);
  1544. user_dce=atol(str);
  1545.  
  1546. str[0]=0;
  1547. fgets(str,81,stream);            /* exec dir */
  1548. if(!str[0])
  1549.     sprintf(exec_dir,"%s..\\EXEC\\",ctrl_dir);
  1550. else {
  1551.     if(str[0]=='.')
  1552.         sprintf(exec_dir,"%s%s",node_dir,str);
  1553.     else
  1554.         sprintf(exec_dir,"%.50s",str); }
  1555. truncsp(exec_dir);
  1556. if(_fullpath(str,exec_dir,50))
  1557.     strcpy(exec_dir,str);
  1558. backslash(exec_dir);
  1559.  
  1560. str[0]=0;
  1561. fgets(str,81,stream);            /* text dir */
  1562. if(!str[0])
  1563.     sprintf(text_dir,"%s..\\TEXT\\",ctrl_dir);
  1564. else {
  1565.     if(str[0]=='.')
  1566.         sprintf(text_dir,"%s%s",node_dir,str);
  1567.     else
  1568.         sprintf(text_dir,"%.50s",str); }
  1569. truncsp(text_dir);
  1570. if(_fullpath(str,text_dir,50))
  1571.     strcpy(text_dir,str);
  1572. backslash(text_dir);
  1573.  
  1574. str[0]=0;
  1575. fgets(str,81,stream);            /* temp dir */
  1576. if(!str[0])
  1577.     sprintf(temp_dir,"%sTEMP\\",node_dir);
  1578. else {
  1579.     if(str[0]!='\\' && str[1]!=':')
  1580.         sprintf(temp_dir,"%s%s",node_dir,str);
  1581.     else
  1582.         sprintf(temp_dir,"%.50s",str); }
  1583. truncsp(temp_dir);
  1584. if(_fullpath(str,temp_dir,50))
  1585.     strcpy(temp_dir,str);
  1586. backslash(temp_dir);
  1587.  
  1588. str[0]=0;
  1589. fgets(str,81,stream);
  1590. sprintf(sys_id,"%.8s",str);
  1591.  
  1592. str[0]=0;
  1593. fgets(str,81,stream);
  1594. truncsp(str);
  1595. if(str[0])
  1596.     node_misc=(uint)ahtoul(str);
  1597. else
  1598.     node_misc=NM_LOWPRIO;
  1599.  
  1600. fclose(stream);
  1601.  
  1602. sprintf(str,"%sINTRSBBS.DAT",node_dir);     /* Shrank to run! */
  1603. if(fexist(str)) {
  1604.     if((stream=fopen(str,"rt"))==NULL) {
  1605.         printf("Can't open %s\n",str);
  1606.         exit(1); }
  1607.     fgets(tmp,81,stream);                    /* so get MSR address from file */
  1608.     msr=(uint far *)atol(tmp);
  1609.     fclose(stream);
  1610.     remove(str); }
  1611.  
  1612. starttime=time(NULL);            /* initialize start time stamp */
  1613. wordwrap[0]=0;                    /* set wordwrap to null */
  1614. attr(LIGHTGRAY);                /* initialize color and curatr to plain */
  1615. mnehigh=LIGHTGRAY|HIGH;         /* mnemonics highlight color */
  1616. mnelow=GREEN;                    /* mnemonics normal text color */
  1617. sec_warn=180;                    /* seconds till inactivity warning */
  1618. sec_timeout=300;                /* seconds till inactivity timeout */
  1619. tos=lncntr=0;                    /* init topofscreen and linecounter to 0 */
  1620. lastnodemsg=0;                    /* Last node to send message to */
  1621. aborted=0;                        /* Ctrl-C hit flag */
  1622. sysop_level=90;                 /* Minimum level to be considered sysop */
  1623.  
  1624. sprintf(str,"%s%s",ctrl_dir,"NODE.DAB");
  1625. if((nodefile=sopen(str,O_BINARY|O_RDWR,SH_DENYNO))==-1) {
  1626.     bprintf("\r\n\7Error opening %s\r\n",str);
  1627.     exit(1); }
  1628.  
  1629. sprintf(str,"%sUSER\\NAME.DAT",data_dir);
  1630. if((i=nopen(str,O_RDONLY))==-1) {
  1631.     printf("\r\n\7Error opening %s\r\n",str);
  1632.     exit(1); }
  1633. memset(str,0,30);
  1634. read(i,str,26);
  1635. close(i);
  1636. if(str[25]==CR)     /* Version 1b */
  1637.     name_len=25;
  1638. else                /* Version 1a */
  1639.     name_len=30;
  1640. }
  1641.  
  1642. /****************************************************************************/
  1643. /* Automatic RIP & WIP terminal detection function. Sets RIP and WIP bits    */
  1644. /* in user_misc variable. Must be called AFTER initdat(), not before.        */
  1645. /****************************************************************************/
  1646. void get_term(void)
  1647. {
  1648.     char str[128],ch;
  1649.     int i;
  1650.  
  1651. bputs("\r\x1b[!_\x1b[0t_\r        \r");
  1652. mswait(500);
  1653. for(i=0;i<120;i++) {
  1654.     ch=inkey(0);
  1655.     if(!ch)
  1656.         break;
  1657.     mswait(1);
  1658.     str[i]=ch; }
  1659. str[i]=0;
  1660. if(strstr(str,"RIPSCRIP"))
  1661.     user_misc|=RIP;
  1662. if(strstr(str,"DC-TERM"))
  1663.     user_misc|=WIP;
  1664. }
  1665.  
  1666. /****************************************************************************/
  1667. /* Truncates white-space chars off end of 'str' and terminates at first tab */
  1668. /****************************************************************************/
  1669. void truncsp(uchar *str)
  1670. {
  1671.     char c;
  1672.  
  1673. str[strcspn(str,"\t")]=0;
  1674. c=strlen(str);
  1675. while(c && (uchar)str[c-1]<=SP) c--;
  1676. str[c]=0;
  1677. }
  1678.  
  1679. /****************************************************************************/
  1680. /* Puts a backslash on path strings                                         */
  1681. /****************************************************************************/
  1682. void backslash(char *str)
  1683. {
  1684.     int i;
  1685.  
  1686. i=strlen(str);
  1687. if(i && str[i-1]!='\\') {
  1688.     str[i]='\\'; str[i+1]=0; }
  1689. }
  1690.  
  1691.  
  1692. /****************************************************************************/
  1693. /* Checks the amount of time inside the external program against the amount */
  1694. /* of time the user had left online before running the external program and */
  1695. /* prints a message and exits the program if time has run out.                */
  1696. /****************************************************************************/
  1697. void checktimeleft(void)
  1698. {
  1699. if(!SYSOP && !strchr(user_exempt,'T') && time(NULL)-starttime>timeleft) {
  1700.     bputs("\1_\n\1r\1hTime's up.\n");
  1701.     exit(0);  }
  1702. }
  1703.  
  1704. /****************************************************************************/
  1705. /* Prints a file remotely and locally, interpreting ^A sequences.            */
  1706. /* 'str' is the path of the file to print                                   */
  1707. /****************************************************************************/
  1708. void printfile(char *str)
  1709. {
  1710.     char *buf;
  1711.     int file;
  1712.     ulong length;
  1713.  
  1714. strupr(str);
  1715. if(!tos)
  1716.     CRLF;
  1717. if((file=nopen(str,O_RDONLY))==-1) {
  1718.     bprintf("File not Found: %s\r\n",str);
  1719.     return; }
  1720. length=filelength(file);
  1721. if((buf=MALLOC(length+1L))==NULL) {
  1722.     close(file);
  1723.     bprintf("\7\r\nPRINTFILE: Error allocating %lu bytes of memory for %s.\r\n"
  1724.         ,length+1L,str);
  1725.     return; }
  1726. buf[read(file,buf,length)]=0;
  1727. close(file);
  1728. bputs(buf);
  1729. aborted=0;
  1730. FREE(buf);
  1731. }
  1732.  
  1733. /****************************************************************************/
  1734. /* Returns a char pointer to the name of the user that corresponds to        */
  1735. /* usernumber. Takes value directly from database.                            */
  1736. /****************************************************************************/
  1737. char *username(uint usernumber)
  1738. {
  1739.     static    char name[26];
  1740.     char    str[128];
  1741.     int     i,file;
  1742.  
  1743. strcpy(name,"UNKNOWN USER");
  1744. if(!data_dir[0])
  1745.     return(name);
  1746. if(!usernumber) {
  1747.     bputs("\7username: called with zero usernumber\r\n");
  1748.     return(name); }
  1749. sprintf(str,"%sUSER\\NAME.DAT",data_dir);
  1750. if((file=nopen(str,O_RDONLY))==-1) {
  1751.     bprintf("\7username: couldn't open %s\r\n",str);
  1752.     return(name); }
  1753. if(filelength(file)<(long)(usernumber-1)*((long)name_len+2L)) {
  1754.     close(file);
  1755.     return(name); }
  1756. lseek(file,(long)(usernumber-1)*((long)name_len+2L),SEEK_SET);
  1757. read(file,name,25);
  1758. close(file);
  1759. for(i=0;i<25;i++)
  1760.     if(name[i]==3)
  1761.         break;
  1762. name[i]=0;
  1763. if(!name[0])
  1764.     strcpy(name,"DELETED USER");
  1765. return(name);
  1766. }
  1767.  
  1768. /****************************************************************************/
  1769. /* Returns the number of the user 'username' from the NAME.DAT file.        */
  1770. /* If the username is not found, the function returns 0.                    */
  1771. /****************************************************************************/
  1772. uint usernumber(char *username)
  1773. {
  1774.     char str[128];
  1775.     int i,file;
  1776.     FILE *stream;
  1777.  
  1778. if(!data_dir[0])
  1779.     return(0);
  1780. sprintf(str,"%sUSER\\NAME.DAT",data_dir);
  1781. if((file=nopen(str,O_RDONLY))==-1 || (stream=fdopen(file,"rb"))==NULL) {
  1782.     if(file!=-1)
  1783.         close(file);
  1784.     bprintf("\7usernumber: couldn't open %s\r\n",str);
  1785.     return(0); }
  1786. for(i=1;!feof(stream);i++) {
  1787.     if(!fread(str,27,1,stream))
  1788.         break;
  1789.     str[25]=0;
  1790.     truncsp(str);    /* chop of trailing EOTs and spaces */
  1791.     if(!stricmp(str,username)) {
  1792.         fclose(stream);
  1793.         return(i); } }
  1794. fclose(stream);
  1795. return(0);
  1796. }
  1797.  
  1798.  
  1799. /****************************************************************************/
  1800. /* Checks the disk drive for the existance of a file. Returns 1 if it         */
  1801. /* exists, 0 if it doesn't.                                                    */
  1802. /* Called from upload                                                        */
  1803. /****************************************************************************/
  1804. char fexist(char *filespec)
  1805. {
  1806. #ifdef __SC__    /* Symantec */
  1807. if(findfirst(filespec,0)==NULL)
  1808.     return(0);
  1809. return(1);
  1810. #else            /* Not Symantec */
  1811.     struct ffblk f;
  1812.  
  1813. if(findfirst(filespec,&f,0)==NULL)
  1814.     return(1);
  1815. return(0);
  1816. #endif            /* !__SC__ */
  1817. }
  1818.  
  1819. /****************************************************************************/
  1820. /* Returns the length of the first file found that matches 'filespec'       */
  1821. /* -1 if the file doesn't exist.                                            */
  1822. /****************************************************************************/
  1823. long flength(char *filespec)
  1824. {
  1825. #ifdef __SC__        /* Symantec */
  1826.     struct FILE *f;
  1827.  
  1828. if((f=findfirst(filespec,0))==NULL)
  1829.     return(-1);
  1830. return(f->size);
  1831.  
  1832. #else                /* Not Symantec */
  1833.  
  1834.     struct ffblk f;
  1835.  
  1836. if(findfirst(filespec,&f,0)==NULL)
  1837. #ifdef __TURBOC__    /* Borland */
  1838.     return(f.ff_fsize);
  1839. #else                /* Other (Watcom) */
  1840.     return(f.size);
  1841. #endif
  1842. return(-1L);
  1843. #endif                /* !__SC__ */
  1844. }
  1845.  
  1846. /****************************************************************************/
  1847. /* Returns in 'string' a character representation of the number in l with   */
  1848. /* commas. Maximum value of l is 4 gigabytes.                                */
  1849. /****************************************************************************/
  1850. char *ultoac(ulong l, char *string)
  1851. {
  1852.     char str[81];
  1853.     char i,j,k;
  1854.  
  1855. ultoa(l,str,10);
  1856. i=strlen(str)-1;
  1857. j=i/3+1+i;
  1858. string[j--]=0;
  1859. for(k=1;i>-1;k++) {
  1860.     string[j--]=str[i--];
  1861.     if(j>0 && !(k%3))
  1862.         string[j--]=','; }
  1863. return(string);
  1864. }
  1865.  
  1866. /****************************************************************************/
  1867. /* Converts an ASCII Hex string into a ulong                                */
  1868. /****************************************************************************/
  1869. ulong ahtoul(char *str)
  1870. {
  1871.     ulong l,val=0;
  1872.  
  1873. while((l=(*str++)|0x20)!=0x20)
  1874.     val=(l&0xf)+(l>>6&1)*9+val*16;
  1875. return(val);
  1876. }
  1877.  
  1878. /****************************************************************************/
  1879. /* Reads the data for node number 'number' into the structure 'node'        */
  1880. /* from NODE.DAB                                                            */
  1881. /* if lockit is non-zero, locks this node's record. putnodedat() unlocks it */
  1882. /****************************************************************************/
  1883. void getnodedat(uchar number, node_t *node, char lockit)
  1884. {
  1885.     char str[256];
  1886.     int count=0;
  1887.  
  1888. if(nodefile<0)
  1889.     return;
  1890. number--;    /* make zero based */
  1891. while(count<LOOP_NODEDAB) {
  1892.     lseek(nodefile,(long)number*sizeof(node_t),SEEK_SET);
  1893.     if(lockit
  1894.         && lock(nodefile,(long)number*sizeof(node_t),sizeof(node_t))==-1) {
  1895.         count++;
  1896.         continue; }
  1897.     if(read(nodefile,node,sizeof(node_t))==sizeof(node_t))
  1898.         break;
  1899.     count++; }
  1900. if(count==LOOP_NODEDAB)
  1901.     bprintf("\7Error unlocking and reading NODE.DAB\r\n");
  1902. }
  1903.  
  1904. /****************************************************************************/
  1905. /* Write the data from the structure 'node' into NODE.DAB                      */
  1906. /* getnodedat(num,&node,1); must have been called before calling this func  */
  1907. /*          NOTE: ------^   the indicates the node record has been locked   */
  1908. /****************************************************************************/
  1909. void putnodedat(uchar number, node_t node)
  1910. {
  1911.     char str[256];
  1912.     int count;
  1913.  
  1914. if(nodefile<0)
  1915.     return;
  1916. number--;    /* make zero based */
  1917. lseek(nodefile,(long)number*sizeof(node_t),SEEK_SET);
  1918. if(write(nodefile,&node,sizeof(node_t))!=sizeof(node_t)) {
  1919.     unlock(nodefile,(long)number*sizeof(node_t),sizeof(node_t));
  1920.     bprintf("\7Error writing NODE.DAB for node %u\r\n",number+1);
  1921.     return; }
  1922. unlock(nodefile,(long)number*sizeof(node_t),sizeof(node_t));
  1923. }
  1924.  
  1925. /****************************************************************************/
  1926. /* Checks for messages waiting for this node or interruption.                */
  1927. /****************************************************************************/
  1928. void nodesync(void)
  1929. {
  1930.     node_t node;
  1931.  
  1932. if(!ctrl_dir[0])
  1933.     return;
  1934. getnodedat(node_num,&node,0);
  1935.  
  1936. if(node.misc&NODE_MSGW)
  1937.     getsmsg(user_number);        /* getsmsg clears MSGW flag */
  1938.  
  1939. if(node.misc&NODE_NMSG)         /* getnmsg clears NMSG flag */
  1940.     getnmsg();
  1941.  
  1942. if(node.misc&NODE_INTR)
  1943.     exit(0);
  1944.  
  1945. }
  1946.  
  1947. /****************************************************************************/
  1948. /* Displays the information for node number 'number' contained in 'node'    */
  1949. /****************************************************************************/
  1950. void printnodedat(uchar number, node_t node)
  1951. {
  1952.     char hour,mer[3],tmp[256];
  1953.     int i;
  1954.  
  1955. attr(LIGHTGRAY|HIGH);
  1956. bprintf("Node %2d: ",number);
  1957. attr(GREEN);
  1958. switch(node.status) {
  1959.     case NODE_WFC:
  1960.         bputs("Waiting for call");
  1961.         break;
  1962.     case NODE_OFFLINE:
  1963.         bputs("Offline");
  1964.         break;
  1965.     case NODE_NETTING:
  1966.         bputs("Networking");
  1967.         break;
  1968.     case NODE_LOGON:
  1969.         bputs("At logon prompt");
  1970.         break;
  1971.     case NODE_EVENT_WAITING:
  1972.         bputs("Waiting for all nodes to become inactive");
  1973.         break;
  1974.     case NODE_EVENT_LIMBO:
  1975.         bprintf("Waiting for node %d to finish external event",node.aux);
  1976.         break;
  1977.     case NODE_EVENT_RUNNING:
  1978.         bputs("Running external event");
  1979.         break;
  1980.     case NODE_NEWUSER:
  1981.         attr(GREEN|HIGH);
  1982.         bputs("New user");
  1983.         attr(GREEN);
  1984.         bputs(" applying for access ");
  1985.         if(!node.connection)
  1986.             bputs("Locally");
  1987.         else
  1988.             bprintf("at %ubps",node.connection);
  1989.         break;
  1990.     case NODE_QUIET:
  1991.         if(!SYSOP) {
  1992.             bputs("Waiting for call");
  1993.             break; }
  1994.     case NODE_INUSE:
  1995.         attr(GREEN|HIGH);
  1996.         if(node.misc&NODE_ANON && !SYSOP)
  1997.             bputs("UNKNOWN USER");
  1998.         else
  1999.             bputs(username(node.useron));
  2000.         attr(GREEN);
  2001.         bputs(" ");
  2002.         switch(node.action) {
  2003.             case NODE_MAIN:
  2004.                 bputs("at main menu");
  2005.                 break;
  2006.             case NODE_RMSG:
  2007.                 bputs("reading messages");
  2008.                 break;
  2009.             case NODE_RMAL:
  2010.                 bputs("reading mail");
  2011.                 break;
  2012.             case NODE_RSML:
  2013.                 bputs("reading sent mail");
  2014.                 break;
  2015.             case NODE_RTXT:
  2016.                 bputs("reading text files");
  2017.                 break;
  2018.             case NODE_PMSG:
  2019.                 bputs("posting message");
  2020.                 break;
  2021.             case NODE_SMAL:
  2022.                 bputs("sending mail");
  2023.                 break;
  2024.             case NODE_AMSG:
  2025.                 bputs("posting auto-message");
  2026.                 break;
  2027.             case NODE_XTRN:
  2028.                 if(!node.aux)
  2029.                     bputs("at external program menu");
  2030.                 else {
  2031.                     bputs("running ");
  2032.                     i=node.aux-1;
  2033.                     if(i>=total_xtrns || !xtrn[i][0])
  2034.                         bputs("external program");
  2035.                     else
  2036.                         bputs(xtrn[i]); }
  2037.                 break;
  2038.             case NODE_DFLT:
  2039.                 bputs("changing defaults");
  2040.                 break;
  2041.             case NODE_XFER:
  2042.                 bputs("at transfer menu");
  2043.                 break;
  2044.             case NODE_RFSD:
  2045.                 bprintf("retrieving from device #%d",node.aux);
  2046.                 break;
  2047.             case NODE_DLNG:
  2048.                 bprintf("downloading");
  2049.                 break;
  2050.             case NODE_ULNG:
  2051.                 bputs("uploading");
  2052.                 break;
  2053.             case NODE_BXFR:
  2054.                 bputs("transferring bidirectional");
  2055.                 break;
  2056.             case NODE_LFIL:
  2057.                 bputs("listing files");
  2058.                 break;
  2059.             case NODE_LOGN:
  2060.                 bputs("logging on");
  2061.                 break;
  2062.             case NODE_LCHT:
  2063.                 bprintf("in local chat with %s",sys_op);
  2064.                 break;
  2065.             case NODE_MCHT:
  2066.                 if(node.aux) {
  2067.                     bprintf("in multinode chat channel %d",node.aux&0xff);
  2068.                     if(node.aux&0x1f00)  /* password */
  2069.                         outchar('*'); }
  2070.                 else
  2071.                     bputs("in multinode global chat channel");
  2072.                 break;
  2073.             case NODE_PAGE:
  2074.                 bprintf("paging node %u for private chat",node.aux);
  2075.                 break;
  2076.             case NODE_PCHT:
  2077.                 bprintf("in private chat with node %u",node.aux);
  2078.                 break;
  2079.             case NODE_GCHT:
  2080.                 bprintf("chatting with %s",sys_guru);
  2081.                 break;
  2082.             case NODE_CHAT:
  2083.                 bputs("in chat section");
  2084.                 break;
  2085.             case NODE_TQWK:
  2086.                 bputs("transferring QWK packet");
  2087.                 break;
  2088.             case NODE_SYSP:
  2089.                 bputs("performing sysop activities");
  2090.                 break;
  2091.             default:
  2092.                 bputs(itoa(node.action,tmp,10));
  2093.                 break;  }
  2094.         if(!node.connection)
  2095.             bputs(" locally");
  2096.         else
  2097.             bprintf(" at %ubps",node.connection);
  2098.         if(node.action==NODE_DLNG) {
  2099.             if((node.aux/60)>12) {
  2100.                 hour=(node.aux/60)-12;
  2101.                 strcpy(mer,"pm"); }
  2102.             else {
  2103.                 if((node.aux/60)==0)    /* 12 midnite */
  2104.                     hour=12;
  2105.                 else hour=node.aux/60;
  2106.                 strcpy(mer,"am"); }
  2107.             bprintf(" ETA %02d:%02d %s"
  2108.                 ,hour,node.aux%60,mer); }
  2109.         break; }
  2110. i=NODE_LOCK;
  2111. if(node.status==NODE_INUSE || SYSOP)
  2112.     i|=NODE_POFF|NODE_AOFF|NODE_MSGW|NODE_NMSG;
  2113. if(node.misc&i) {
  2114.     bputs(" (");
  2115.     if(node.misc&(i&NODE_AOFF))
  2116.         outchar('A');
  2117.     if(node.misc&NODE_LOCK)
  2118.         outchar('L');
  2119.     if(node.misc&(i&(NODE_MSGW|NODE_NMSG)))
  2120.         outchar('M');
  2121.     if(node.misc&(i&NODE_POFF))
  2122.         outchar('P');
  2123.     outchar(')'); }
  2124. if(SYSOP && ((node.misc
  2125.     &(NODE_ANON|NODE_UDAT|NODE_INTR|NODE_RRUN|NODE_EVENT|NODE_DOWN))
  2126.     || node.status==NODE_QUIET)) {
  2127.     bputs(" [");
  2128.     if(node.misc&NODE_ANON)
  2129.         outchar('A');
  2130.     if(node.misc&NODE_INTR)
  2131.         outchar('I');
  2132.     if(node.misc&NODE_RRUN)
  2133.         outchar('R');
  2134.     if(node.misc&NODE_UDAT)
  2135.         outchar('U');
  2136.     if(node.status==NODE_QUIET)
  2137.         outchar('Q');
  2138.     if(node.misc&NODE_EVENT)
  2139.         outchar('E');
  2140.     if(node.misc&NODE_DOWN)
  2141.         outchar('D');
  2142.     outchar(']'); }
  2143. if(node.errors && SYSOP) {
  2144.     attr(RED|HIGH|BLINK);
  2145.     bprintf(" %d error%c",node.errors, node.errors>1 ? 's' : '\0' ); }
  2146. CRLF;
  2147. }
  2148.  
  2149. /****************************************************************************/
  2150. /* Prints short messages waiting for 'usernumber', if any...                */
  2151. /* then deletes them.                                                        */
  2152. /****************************************************************************/
  2153. void getsmsg(int usernumber)
  2154. {
  2155.     char str[256], *buf;
  2156.     int file;
  2157.     long length;
  2158.     node_t node;
  2159.  
  2160. if(!data_dir[0])
  2161.     return;
  2162. sprintf(str,"%sMSGS\\%4.4u.MSG",data_dir,usernumber);
  2163. if(flength(str)<1L) {
  2164.     return; }
  2165. if((file=nopen(str,O_RDWR))==-1) {
  2166.     bprintf("\7Error opening %s for read/write access\r\n",str);
  2167.     return; }
  2168. length=filelength(file);
  2169. if((buf=MALLOC(length+1))==NULL) {
  2170.     close(file);
  2171.     bprintf("\7Error allocating %u bytes of memory for %s\r\n",length+1,str);
  2172.     return; }
  2173. if(read(file,buf,length)!=length) {
  2174.     close(file);
  2175.     FREE(buf);
  2176.     bprintf("\7Error reading %u bytes from %s\r\n",length,str);
  2177.     return; }
  2178. chsize(file,0L);
  2179. close(file);
  2180. buf[length]=0;
  2181. getnodedat(node_num,&node,0);
  2182. if(node.action==NODE_MAIN || node.action==NODE_XFER) {
  2183.     CRLF; }
  2184. if(node.misc&NODE_MSGW) {
  2185.     getnodedat(node_num,&node,1);
  2186.     node.misc&=~NODE_MSGW;
  2187.     putnodedat(node_num,node); }
  2188. bputs(buf);
  2189. FREE(buf);
  2190. }
  2191.  
  2192. /****************************************************************************/
  2193. /* Creates a short message for 'usernumber' than contains 'strin'            */
  2194. /****************************************************************************/
  2195. void putsmsg(int usernumber, char *strin)
  2196. {
  2197.     char str[256];
  2198.     int file,i;
  2199.     node_t node;
  2200.  
  2201. if(!data_dir[0])
  2202.     return;
  2203. sprintf(str,"%sMSGS\\%4.4u.MSG",data_dir,usernumber);
  2204. if((file=nopen(str,O_WRONLY|O_CREAT|O_APPEND))==-1) {
  2205.     bprintf("\7Error opening/creating %s for creat/append access\r\n",str);
  2206.     return; }
  2207. i=strlen(strin);
  2208. if(write(file,strin,i)!=i) {
  2209.     close(file);
  2210.     bprintf("\7Error writing %u bytes to %s\r\n",i,str);
  2211.     return; }
  2212. close(file);
  2213. for(i=1;i<=sys_nodes;i++) {        /* flag node if user on that msg waiting */
  2214.     getnodedat(i,&node,0);
  2215.     if(node.useron==usernumber
  2216.         && (node.status==NODE_INUSE || node.status==NODE_QUIET)
  2217.         && !(node.misc&NODE_MSGW)) {
  2218.         getnodedat(i,&node,1);
  2219.         node.misc|=NODE_MSGW;
  2220.         putnodedat(i,node); } }
  2221. }
  2222.  
  2223. /****************************************************************************/
  2224. /* Prints short messages waiting for this node, if any...                    */
  2225. /****************************************************************************/
  2226. void getnmsg(void)
  2227. {
  2228.     char str[256], *buf;
  2229.     int file;
  2230.     long length;
  2231.     node_t thisnode;
  2232.  
  2233. if(!data_dir[0])
  2234.     return;
  2235. getnodedat(node_num,&thisnode,1);
  2236. thisnode.misc&=~NODE_NMSG;            /* clear the NMSG flag */
  2237. putnodedat(node_num,thisnode);
  2238.  
  2239. sprintf(str,"%sMSGS\\N%3.3u.MSG",data_dir,node_num);
  2240. if(flength(str)<1L) {
  2241.     return; }
  2242. if((file=nopen(str,O_RDWR))==-1) {
  2243.     printf("Couldn't open %s for read/write\r\n",str);
  2244.     return; }
  2245. length=filelength(file);
  2246. if((buf=MALLOC(length+1))==NULL) {
  2247.     close(file);
  2248.     printf("Couldn't allocate %lu bytes for %s\r\n",length+1,str);
  2249.     return; }
  2250. if(read(file,buf,length)!=length) {
  2251.     close(file);
  2252.     FREE(buf);
  2253.     printf("Couldn't read %lu bytes from %s\r\n",length,str);
  2254.     return; }
  2255. chsize(file,0L);
  2256. close(file);
  2257. buf[length]=0;
  2258.  
  2259. bputs(buf);
  2260. FREE(buf);
  2261. }
  2262.  
  2263. /****************************************************************************/
  2264. /* Creates a short message for node 'num' than contains 'strin'             */
  2265. /****************************************************************************/
  2266. void putnmsg(int num, char *strin)
  2267. {
  2268.     char str[256];
  2269.     int file,i;
  2270.     node_t node;
  2271.  
  2272. if(!data_dir[0])
  2273.     return;
  2274. sprintf(str,"%sMSGS\\N%3.3u.MSG",data_dir,num);
  2275. if((file=nopen(str,O_WRONLY|O_CREAT|O_APPEND))==-1) {
  2276.     printf("Couldn't open %s for append\r\n",str);
  2277.     return; }
  2278. i=strlen(strin);
  2279. if(write(file,strin,i)!=i) {
  2280.     close(file);
  2281.     printf("Error writing %u bytes to %s\r\n",i,str);
  2282.     return; }
  2283. close(file);
  2284. getnodedat(num,&node,0);
  2285. if((node.status==NODE_INUSE || node.status==NODE_QUIET)
  2286.     && !(node.misc&NODE_NMSG)) {
  2287.     getnodedat(num,&node,1);
  2288.     node.misc|=NODE_NMSG;
  2289.     putnodedat(num,node); }
  2290. }
  2291.  
  2292. /****************************************************************************/
  2293. /* This function lists users that are online.                                */
  2294. /* If listself is true, it will list the current node.                        */
  2295. /* Returns number of active nodes (not including current node).             */
  2296. /****************************************************************************/
  2297. int whos_online(char listself)
  2298. {
  2299.     int i,j;
  2300.     node_t node;
  2301.  
  2302. if(!ctrl_dir[0])
  2303.     return(0);
  2304. CRLF;
  2305. for(j=0,i=1;i<=sys_nodes;i++) {
  2306.     getnodedat(i,&node,0);
  2307.     if(i==node_num) {
  2308.         if(listself)
  2309.             printnodedat(i,node);
  2310.         continue; }
  2311.     if(node.status==NODE_INUSE || (SYSOP && node.status==NODE_QUIET)) {
  2312.         printnodedat(i,node);
  2313.         if(!lastnodemsg)
  2314.             lastnodemsg=i;
  2315.         j++; } }
  2316. if(!j)
  2317.     bputs("\1nNo other active nodes.\r\n");
  2318. return(j);
  2319. }
  2320.  
  2321. /****************************************************************************/
  2322. /* Sending single line messages between nodes                               */
  2323. /****************************************************************************/
  2324. void nodemsg(void)
  2325. {
  2326.     char    str[256],line[256],buf[512];
  2327.     int     i,j;
  2328.     node_t    thisnode;
  2329.     node_t     node;
  2330.  
  2331. if(!ctrl_dir[0])
  2332.     return;
  2333. if(strchr(user_rest,'C')) {
  2334.     bputs("You cannot send messages.\r\n");
  2335.     return; }
  2336. getnodedat(node_num,&thisnode,0);
  2337. wordwrap[0]=0;
  2338. if(lastnodemsg) {
  2339.     getnodedat(lastnodemsg,&node,0);
  2340.     if(node.status!=NODE_INUSE)
  2341.         lastnodemsg=0; }
  2342. if(!whos_online(0))
  2343.     return;
  2344. bprintf("\r\nngNumber of node to send message to, whAngll, "
  2345.     "or whQnguit [%u]: wh",lastnodemsg);
  2346. i=getkeys("QA",sys_nodes);
  2347. if(i==-1)
  2348.     return;
  2349. if(i&0x8000 || !i) {
  2350.     if(!i)
  2351.         i=lastnodemsg;
  2352.     else {
  2353.         i^=0x8000;
  2354.         lastnodemsg=i; }
  2355.     if(!i || i>sys_nodes)
  2356.         return;
  2357.     getnodedat(i,&node,0);
  2358.     if(node.status!=NODE_INUSE && !SYSOP)
  2359.         bprintf("\r\n_whNode %d is not in use.\r\n",i);
  2360.     else if(i==node_num)
  2361.         bputs("\r\nThere's no need to send a message to yourself.\r\n");
  2362.     else if(node.misc&NODE_POFF && !SYSOP)
  2363.         bprintf("\r\nrhiDon't bug %s.n\r\n"
  2364.             ,node.misc&NODE_ANON ? "UNKNOWN USER"
  2365.             : username(node.useron));
  2366.     else {
  2367.         bputs("_yhMessage: ");
  2368.         if(!getstr(line,70,K_LINE))
  2369.             return;
  2370.         sprintf(buf
  2371.             ,"\7_whNode %2d: g%sng sent you a message:\r\nwh4%sn\r\n"
  2372.             ,node_num
  2373.             ,thisnode.misc&NODE_ANON ? "UNKNOWN USER" : user_name,line);
  2374.         putnmsg(i,buf); } }
  2375. else if(i=='A') {
  2376.     bputs("_yhMessage: ");
  2377.     if(!getstr(line,70,K_LINE))
  2378.         return;
  2379.     sprintf(buf
  2380.         ,"\7_whNode %2d: g%sng sent all nodes a message:\r\n"
  2381.             "wh4%sn\r\n"
  2382.         ,node_num
  2383.         ,thisnode.misc&NODE_ANON ? "UNKNOWN USER" : user_name,line);
  2384.     for(i=1;i<=sys_nodes;i++) {
  2385.         if(i==node_num)
  2386.             continue;
  2387.         getnodedat(i,&node,0);
  2388.         if((node.status==NODE_INUSE || (SYSOP && node.status==NODE_QUIET))
  2389.             && (SYSOP || !(node.misc&NODE_POFF)))
  2390.             putnmsg(i,buf); } }
  2391.  
  2392. }
  2393.  
  2394. /****************************************************************************/
  2395. /* Puts a character into the input buffer                                    */
  2396. /****************************************************************************/
  2397. void ungetkey(char ch)
  2398. {
  2399.  
  2400. keybuf[keybuftop++]=ch;
  2401. if(keybuftop==KEY_BUFSIZE)
  2402.     keybuftop=0;
  2403. }
  2404.  
  2405. #ifdef __SC__                    /* Missing from Symantec RTL */
  2406. void clrscr(void) 
  2407. {
  2408.         asm
  2409.         {       mov ah,8        /*function # for "Get char with attr*/
  2410.                 xor bh,bh       /*page 0*/
  2411.                 int 10h         /*Call interrupt 10h (video)*/
  2412.                 
  2413.                 mov bh,ah       /*set "set attr" to "current attr"*/
  2414.                 mov ah,6        /*function # for "Scroll Window Up"*/
  2415.                 xor cx,cx       /*set upper row & column (0,0)*/
  2416.                 xor al,al       /*set "# lines to scroll" to 0*/
  2417.                 mov dh,119      /*set lowqer colum*/
  2418.                 int 10h         /*Call interrupt 10h*/
  2419.                 
  2420.                 mov ah,2        /*function # for "Set Cursor Position"*/
  2421.                 xor bh,bh       /*set page to 0*/
  2422.                 xor dx,dx       /*set row & colum to 0 (upper left)*/
  2423.                 int 10h         /*Call interrupt 10h*/
  2424.         }
  2425.         return;
  2426. }
  2427.  
  2428. short wherey(void)
  2429. {
  2430.         struct disp_t rc;
  2431.         return(rc.cursorcol);
  2432. }               
  2433. #endif  /* __SC__ */
  2434.  
  2435.